import xlsx from 'node-xlsx';
import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import { Response } from 'express';
import { getLogger } from '@/utils/logger';
const writeFileAsync = promisify(fs.writeFile);
const mkdirAsync = promisify(fs.mkdir);

const logger = getLogger('excel-util');

/**
 * Excel字段配置接口
 */
export interface ExcelField {
  /** 表头显示名称 */
  header: string;
  /** 数据字段名 */
  key: string;
  /** 列宽度 */
  width?: number;
  /** 数据类型: string, number, date, datetime, boolean */
  type?: string;
  /** 格式化函数 */
  formatter?: (value: any) => any;
}

/**
 * Excel工具类
 * 提供Excel导入导出功能
 */
export default class ExcelUtil {
  /**
   * 默认列宽度
   */
  private static readonly DEFAULT_COLUMN_WIDTH = 15;
  
  /**
   * 最大单元格行数限制
   */
  private static readonly MAX_ROW_NUM = 65535;

  /**
   * 导出Excel
   * @param data 数据列表
   * @param fields 字段配置
   * @param sheetName 工作表名称
   * @returns 生成的Excel文件Buffer
   */
  public static async exportExcel(
    data: any[],
    fields: ExcelField[],
    sheetName: string = 'Sheet1'
  ): Promise<Buffer> {
    try {
      // 构建Excel数据
      const excelData: any[][] = [];
      
      // 添加表头
      const headers = fields.map(field => field.header);
      excelData.push(headers);
      
      // 添加数据行
      data.forEach(item => {
        const row: any[] = [];
        fields.forEach(field => {
          const value = this.getCellValue(item, field);
          row.push(value);
        });
        excelData.push(row);
      });
      
      // 创建Excel工作表配置选项
      const colsOptions = {
        '!cols': fields.map(field => ({ wch: field.width || this.DEFAULT_COLUMN_WIDTH }))
      };
      
      // 创建工作表
      const buffer = xlsx.build([{
        name: sheetName,
        data: excelData,
        options: colsOptions
      }]);
      
      logger.debug(`Excel导出成功`, {
        sheetName,
        recordCount: data.length,
        fieldCount: fields.length,
        bufferSize: buffer.length
      });
      return buffer;
    } catch (error) {
      logger.error(`导出Excel失败`, {
        error,
        stack: error instanceof Error ? error.stack : undefined,
        sheetName,
        recordCount: data?.length
      });
      throw new Error('导出Excel失败');
    }
  }
  
  /**
   * 将Excel数据发送到HTTP响应
   * @param res Express响应对象
   * @param buffer Excel数据
   * @param filename 文件名
   */
  public static sendExcelResponse(res: Response, buffer: Buffer, filename: string): void {
    // 设置响应头
    res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
    res.setHeader('Content-Disposition', `attachment; filename=${encodeURIComponent(filename)}.xlsx`);
    res.setHeader('Content-Length', buffer.length);
    
    // 发送Excel文件
    res.send(buffer);
    
    logger.debug(`Excel文件已发送到客户端`, { filename, size: buffer.length });
  }
  
  /**
   * 导出Excel到响应
   * @param res Express响应对象
   * @param data 数据列表
   * @param fields 字段配置
   * @param filename 文件名
   * @param sheetName 工作表名称
   */
  public static async exportToResponse(
    res: Response,
    data: any[],
    fields: ExcelField[],
    filename: string,
    sheetName: string = 'Sheet1'
  ): Promise<void> {
    try {
      const buffer = await this.exportExcel(data, fields, sheetName);
      this.sendExcelResponse(res, buffer, filename);
    } catch (error) {
      logger.error(`导出Excel到响应失败`, {
        error,
        stack: error instanceof Error ? error.stack : undefined,
        filename,
        sheetName,
        recordCount: data?.length
      });
      throw new Error('导出Excel到响应失败');
    }
  }
  
  /**
   * 导出Excel到文件
   * @param data 数据列表
   * @param fields 字段配置
   * @param filename 文件名
   * @param dir 保存目录
   * @returns 文件路径
   */
  public static async exportToFile(
    data: any[],
    fields: ExcelField[],
    filename: string,
    dir: string = 'uploads/excel'
  ): Promise<string> {
    try {
      // 确保目录存在
      await this.ensureDirExists(dir);
      
      // 生成Excel二进制数据
      const buffer = await this.exportExcel(data, fields);
      
      // 生成文件路径
      const filePath = path.join(dir, filename);
      
      // 写入文件
      await writeFileAsync(filePath, buffer);
      
      logger.debug(`Excel文件已保存到: ${filePath}`, {
        recordCount: data.length,
        fieldCount: fields.length,
        size: buffer.length
      });
      return filePath;
    } catch (error) {
      logger.error(`导出Excel文件失败`, {
        error,
        stack: error instanceof Error ? error.stack : undefined,
        filename,
        dir,
        recordCount: data?.length
      });
      throw new Error('导出Excel文件失败');
    }
  }
  
  /**
   * 保存数据列表到Excel文件
   * 
   * @param data 数据列表
   * @param fields 字段配置
   * @param sheetName 工作表名称
   * @param filePath 文件保存完整路径
   * @returns 保存的文件路径
   */
  public static async saveExcelFile(
    data: any[],
    fields: ExcelField[],
    sheetName: string = 'Sheet1',
    filePath: string
  ): Promise<string> {
    try {
      // 生成Excel二进制数据
      const buffer = await this.exportExcel(data, fields, sheetName);
      
      // 确保目录存在
      const dir = path.dirname(filePath);
      await this.ensureDirExists(dir);
      
      // 写入文件
      await writeFileAsync(filePath, buffer);
      
      logger.debug(`Excel文件已保存: ${filePath}`, {
        sheetName,
        recordCount: data.length,
        fieldCount: fields.length,
        size: buffer.length
      });
      return filePath;
    } catch (error) {
      logger.error(`保存Excel文件失败`, {
        error,
        stack: error instanceof Error ? error.stack : undefined,
        filePath,
        sheetName,
        recordCount: data?.length
      });
      throw new Error('保存Excel文件失败');
    }
  }
  
  /**
   * 导入Excel文件
   * @param filePath Excel文件路径
   * @param fields 字段映射
   * @param sheetIndex 工作表索引，默认为0
   * @returns 解析后的数据数组
   */
  public static async importExcel<T = any>(
    filePath: string,
    fields: ExcelField[],
    sheetIndex: number = 0
  ): Promise<T[]> {
    try {
      // 解析Excel文件
      const workbook = xlsx.parse(filePath);
      
      // 获取指定的工作表
      if (!workbook || !workbook[sheetIndex]) {
        throw new Error('Excel文件格式不正确或工作表不存在');
      }
      
      const worksheet = workbook[sheetIndex];
      const rows = worksheet.data;
      
      // 至少需要表头和一行数据
      if (rows.length < 2) {
        return [];
      }
      
      // 解析表头，建立列索引与字段的映射
      const headers = rows[0];
      const columnMap = new Map<number, string>();
      
      fields.forEach(field => {
        const colIndex = headers.findIndex((header: string) => header === field.header);
        if (colIndex !== -1) {
          columnMap.set(colIndex, field.key);
        }
      });
      
      // 处理数据行
      const result: T[] = [];
      for (let i = 1; i < rows.length; i++) {
        const row = rows[i];
        const item: any = {};
        
        // 跳过空行
        if (row.every((cell: any) => cell === null || cell === undefined || cell === '')) {
          continue;
        }
        
        // 填充数据
        columnMap.forEach((fieldKey, colIndex) => {
          if (colIndex < row.length) {
            // 获取对应字段的类型信息
            const field = fields.find(f => f.key === fieldKey);
            let cellValue = row[colIndex];
            
            // 根据类型转换值
            if (field && field.type) {
              cellValue = this.convertCellValue(cellValue, field);
            }
            
            item[fieldKey] = cellValue;
          } else {
            item[fieldKey] = null;
          }
        });
        
        result.push(item as T);
      }
      
      logger.debug(`Excel导入成功: ${filePath}`, {
        sheetName: worksheet.name,
        recordCount: result.length,
        columnCount: columnMap.size
      });
      return result;
    } catch (error) {
      logger.error(`导入Excel失败: ${filePath}`, {
        error,
        stack: error instanceof Error ? error.stack : undefined,
        sheetIndex
      });
      throw new Error('导入Excel失败');
    }
  }
  
  /**
   * 生成Excel模板
   * @param fields 字段配置
   * @param sheetName 工作表名称
   * @returns 生成的Excel模板Buffer
   */
  public static async generateTemplate(
    fields: ExcelField[],
    sheetName: string = 'Sheet1'
  ): Promise<Buffer> {
    try {
      // 创建工作表数据
      const data: any[][] = [];
      
      // 添加表头
      const header = fields.map(field => field.header);
      data.push(header);
      
      // 创建工作表
      const colsOptions = {
        '!cols': fields.map(field => ({ 
          wch: field.width || this.DEFAULT_COLUMN_WIDTH 
        }))
      };
      
      const buffer = xlsx.build([{
        name: sheetName,
        data: data,
        options: colsOptions
      }]);
      
      logger.debug(`Excel模板生成成功`, {
        sheetName,
        fieldCount: fields.length,
        bufferSize: buffer.length
      });
      return buffer;
    } catch (error) {
      logger.error(`生成Excel模板失败`, {
        error,
        stack: error instanceof Error ? error.stack : undefined,
        sheetName,
        fieldCount: fields.length
      });
      throw new Error('生成Excel模板失败');
    }
  }

  /**
   * Excel日期数值转JavaScript日期对象
   * Excel日期是从1900年1月1日开始的天数，但有一个1900年2月29日的bug
   * 
   * @param excelDate Excel日期数值
   * @returns JavaScript日期对象
   */
  private static excelDateToJsDate(excelDate: number): Date {
    // Excel的日期起始于1900年1月1日
    const baseDate = new Date(1900, 0, 1);
    
    // Excel错误地认为1900年是闰年，所以如果日期大于60（1900年2月29日），需要减1
    const daysSince1900 = excelDate <= 60 ? excelDate - 1 : excelDate - 2;
    
    // 将天数转换为毫秒
    const milliseconds = daysSince1900 * 24 * 60 * 60 * 1000;
    
    // 创建新日期对象
    return new Date(baseDate.getTime() + milliseconds);
  }

  /**
   * 转换单元格值类型
   * 
   * @param cellValue 单元格值
   * @param field 字段配置
   * @returns 转换后的值
   */
  private static convertCellValue(cellValue: any, field: ExcelField): any {
    if (cellValue === null || cellValue === undefined) {
      return null;
    }
    
    // 根据类型转换值
    if (field.type) {
      switch (field.type.toLowerCase()) {
        case 'date':
        case 'datetime':
          if (typeof cellValue === 'number') {
            // Excel日期数值转换为JavaScript日期
            return this.excelDateToJsDate(cellValue);
          } else if (typeof cellValue === 'string') {
            return new Date(cellValue);
          }
          return cellValue;
        
        case 'number':
          if (typeof cellValue === 'string') {
            return Number(cellValue) || 0;
          }
          return cellValue;
        
        case 'string':
          return String(cellValue);
        
        case 'boolean':
          if (typeof cellValue === 'string') {
            return cellValue.toLowerCase() === 'true' || 
                  cellValue === '1' || 
                  cellValue.toLowerCase() === 'yes' ||
                  cellValue === '是';
          }
          return Boolean(cellValue);
        
        default:
          return cellValue;
      }
    }
    
    return cellValue;
  }
  
  /**
   * 确保目录存在
   * @param dir 目录路径
   */
  private static async ensureDirExists(dir: string): Promise<void> {
    try {
      if (!fs.existsSync(dir)) {
        await mkdirAsync(dir, { recursive: true });
        logger.debug(`创建目录: ${dir}`);
      }
    } catch (error) {
      logger.error(`创建目录失败: ${dir}`, {
        error,
        stack: error instanceof Error ? error.stack : undefined
      });
      throw new Error('创建目录失败');
    }
  }

  /**
   * 从对象中提取值，支持复杂属性路径
   * 例如: "user.profile.name"
   * 
   * @param obj 对象
   * @param field 字段路径
   * @returns 字段值
   */
  public static getFieldValue(obj: any, field: string): any {
    if (!obj || !field) {
      return null;
    }
    
    const fields = field.split('.');
    let value = obj;
    
    for (const f of fields) {
      if (value === null || value === undefined) {
        return null;
      }
      value = value[f];
    }
    
    return value;
  }

  /**
   * 读取Excel文件
   * 
   * @param filePath Excel文件路径
   * @returns 解析后的工作表数据
   */
  public static readExcelFile(filePath: string): any[] {
    try {
      // 读取Excel文件
      const workbook = xlsx.parse(fs.readFileSync(filePath));
      
      if (!workbook || workbook.length === 0) {
        throw new Error('Excel文件无效或没有工作表');
      }
      
      logger.debug(`读取Excel文件成功: ${filePath}`, {
        sheetCount: workbook.length
      });
      return workbook;
    } catch (error) {
      logger.error(`读取Excel文件失败: ${filePath}`, {
        error,
        stack: error instanceof Error ? error.stack : undefined
      });
      throw new Error('读取Excel文件失败');
    }
  }

  /**
   * 读取Excel文件第一个工作表的全部数据（包含表头）
   * 
   * @param filePath Excel文件路径
   * @returns 工作表数据
   */
  public static readExcelData(filePath: string): any[][] {
    try {
      const workbook = this.readExcelFile(filePath);
      const data = workbook[0]?.data || [];
      logger.debug(`读取Excel工作表数据成功: ${filePath}`, {
        rowCount: data.length
      });
      return data;
    } catch (error) {
      logger.error(`读取Excel数据失败: ${filePath}`, {
        error,
        stack: error instanceof Error ? error.stack : undefined
      });
      throw new Error('读取Excel数据失败');
    }
  }

  /**
   * 创建Excel解析配置
   * 
   * @param fields 字段配置
   * @returns 配置对象
   */
  public static createImportConfig(fields: ExcelField[]): any {
    const fieldMap: Record<number, string> = {};
    const typeMap: Record<string, string> = {};
    
    fields.forEach((field, index) => {
      fieldMap[index] = field.key;
      if (field.type) {
        typeMap[field.key] = field.type;
      }
    });
    
    return {
      fieldMap,
      typeMap
    };
  }

  /**
   * 获取单元格值并进行格式化
   * 
   * @param item 数据对象
   * @param field 字段配置
   * @returns 格式化后的单元格值
   */
  private static getCellValue(item: any, field: ExcelField): any {
    const fieldValue = this.getFieldValue(item, field.key);
    
    // 处理null或undefined
    if (fieldValue === null || fieldValue === undefined) {
      return null;
    }
    
    // 应用格式化函数
    if (field.formatter && typeof field.formatter === 'function') {
      return field.formatter(fieldValue);
    }
    
    // 根据类型格式化值
    if (field.type) {
      switch (field.type.toLowerCase()) {
        case 'date':
          if (fieldValue instanceof Date) {
            return fieldValue.toLocaleDateString();
          }
          return fieldValue;
        
        case 'datetime':
          if (fieldValue instanceof Date) {
            return fieldValue.toLocaleString();
          }
          return fieldValue;
        
        case 'number':
          if (typeof fieldValue === 'number') {
            return fieldValue;
          }
          return Number(fieldValue) || 0;
        
        case 'string':
          return String(fieldValue);
        
        case 'boolean':
          if (typeof fieldValue === 'boolean') {
            return fieldValue ? '是' : '否';
          }
          return fieldValue === 'true' || fieldValue === '1' || fieldValue === 'yes' ? '是' : '否';
        
        default:
          return fieldValue;
      }
    }
    
    return fieldValue;
  }
} 